using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Net;
using System.Web;
using System.Xml;
using System.Xml.Linq;
using WebHelper.Helpers;

namespace WebHelper.Handlers
{
    [Serializable]
    public enum FlipType
    {
        None, X, Y, XY
    }
    [Serializable]
    public class ImageEdittingHandler : IHttpHandler
    {
        #region Static Implementation

        public static string DefaultPath
        {
            get
            {
                return "ImageEditting.axd";
            }
        }
        public static string URl
        {
            get
            {
                return "U";
            }
        }
        public static string MaximumWidth
        {
            get
            {
                return "MW";
            }
        }
        public static string MaximumHeight
        {
            get
            {
                return "MH";
            }
        }
        public static string Width
        {
            get
            {
                return "W";
            }
        }
        public static string Height
        {
            get
            {
                return "H";
            }
        }
        public static string RotateAngle
        {
            get
            {
                return "RA";
            }
        }
        public static string Flip
        {
            get
            {
                return "F";
            }
        }

        private static double DegreeToRadian(double angle)
        {
            return Math.PI * angle / 180.0;
        }
        private static PointF RotatePoint(PointF pnt, PointF origin, float degreeAngle)
        {
            double radAngle = DegreeToRadian(degreeAngle);

            PointF newPoint = new PointF();

            double deltaX = pnt.X - origin.X;
            double deltaY = pnt.Y - origin.Y;

            newPoint.X = (float)(origin.X + (Math.Cos(radAngle) * deltaX - Math.Sin(radAngle) * deltaY));
            newPoint.Y = (float)(origin.Y + (Math.Sin(radAngle) * deltaX + Math.Cos(radAngle) * deltaY));

            return newPoint;
        }
        private static PointF[] RotatePoints(PointF[] pnts, PointF origin, float degreeAngle)
        {
            List<PointF> list = new List<PointF>();
            for (int i = 0; i < pnts.Length; i++)
            {
                list.Add(RotatePoint(pnts[i], origin, degreeAngle));
            }
            return list.ToArray();
        }
        private static RectangleF GetBounds(PointF[] pnts)
        {
            float left = pnts[0].X;
            float right = pnts[0].X;
            float top = pnts[0].Y;
            float bottom = pnts[0].Y;

            for (int i = 1; i < pnts.Length; i++)
            {
                if (pnts[i].X < left)
                    left = pnts[i].X;
                else if (pnts[i].X > right)
                    right = pnts[i].X;

                if (pnts[i].Y < top)
                    top = pnts[i].Y;
                else if (pnts[i].Y > bottom)
                    bottom = pnts[i].Y;
            }

            return new RectangleF(left,
                                  top,
                                  Math.Abs(right - left),
                                 Math.Abs(bottom - top));
        }
        private static Bitmap rotate(Bitmap originalBitmap, float angle)
        {
            //Corners of the image
            PointF[] Points = { new PointF(0, 0),
                                        new PointF(originalBitmap.Width, 0),
                                        new PointF(0, originalBitmap.Height),
                                        new PointF(originalBitmap.Width, originalBitmap.Height)};

            //new corners
            PointF[] newPoints = ImageEdittingHandler.RotatePoints(Points, new PointF(originalBitmap.Width / 2.0f, originalBitmap.Height / 2.0f), angle);


            Bitmap newBitmap = new Bitmap((int)ImageEdittingHandler.GetBounds(newPoints).Width, (int)ImageEdittingHandler.GetBounds(newPoints).Height);
            newBitmap.SetResolution(originalBitmap.HorizontalResolution, originalBitmap.VerticalResolution);
            Graphics g = Graphics.FromImage(newBitmap);
            g.SmoothingMode = SmoothingMode.HighQuality;
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;
            g.PixelOffsetMode = PixelOffsetMode.HighQuality;
            g.CompositingQuality = CompositingQuality.HighQuality;
            Matrix m = new Matrix();
            m.RotateAt(angle, new PointF(originalBitmap.Width / 2.0f, originalBitmap.Height / 2.0f));
            m.Translate(-ImageEdittingHandler.GetBounds(newPoints).Left, -ImageEdittingHandler.GetBounds(newPoints).Top, MatrixOrder.Append); //shift to compensate for the rotation
            g.Transform = m;
            g.DrawImage(originalBitmap, 0, 0);
            return newBitmap;
        }
        private static bool IsResourceModified(DateTime currentDate, HttpRequest request)
        {
            bool ResourceModified = true;
            DateTime requestModifydate;
            // Check If-Modified-Since request header, if it exists 
            if (!string.IsNullOrEmpty(request.Headers["If-Modified-Since"]) && DateTime.TryParse(request.Headers["If-Modified-Since"], out requestModifydate))
            {
                ResourceModified = (currentDate.ToString("yyyyMMddHHmmss") != requestModifydate.ToString("yyyyMMddHHmmss"));
            }
            bool ETagChanged = true;
            if (!string.IsNullOrEmpty(request.Headers["If-None-Match"]))
            {
                ETagChanged = request.Headers["If-None-Match"] != "\"ImageEdittingHandler" + currentDate.ToString("yyyyMMddHHmmss") + "\"";
            }

            return ResourceModified && ETagChanged;
        }


        public static string Register(bool writeToWebConfig,string filePath)
        {
            string path = ImageEdittingHandler.DefaultPath;
            if (string.IsNullOrEmpty(filePath))
            {
                filePath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
            }
            XDocument document = XDocument.Load(filePath);
            XElement httpHandlers = XmlHelper.CreateIfNotExist(document.Root, "system.web", "httpHandlers");
            var configElement = httpHandlers.Elements("add").FirstOrDefault(e => e.Attribute("type").Value.Contains(typeof(ImageEdittingHandler).FullName));
            if (configElement!=null)
            {
                path = configElement.Attribute("path").Value;
                writeToWebConfig = false;
            }
            XElement handlers = XmlHelper.CreateIfNotExist(document.Root, "system.webServer", "handlers");
            configElement = handlers.Elements("add").FirstOrDefault(e => e.Attribute("type") != null && e.Attribute("type").Value.Contains(typeof(ImageEdittingHandler).FullName));
            if (configElement!=null)
            {
                path = configElement.Attribute("path").Value;
                writeToWebConfig = false;
            }
            //iis6
            configElement = httpHandlers.Elements("remove").FirstOrDefault(e => e.Attribute("name") != null && e.Attribute("name").Value.Contains(typeof(ImageEdittingHandler).FullName));
            XElement element = new XElement("add",
                new XAttribute("path", path),
                new XAttribute("type", typeof(ImageEdittingHandler).FullName + "," + typeof(ImageEdittingHandler).Assembly.FullName),
                new XAttribute("verb", "*"),
                new XAttribute("validate", "false"));
            httpHandlers.Add(element);

            //trace
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "register in system.web", typeof(ImageEdittingHandler).Name + "  ");


            //iis7
            configElement = handlers.Elements("remove").FirstOrDefault(e => e.Attribute("name") != null && e.Attribute("name").Value.Contains(typeof(ImageEdittingHandler).FullName));
            if (configElement == null)
            {
                if (handlers.Elements("remove").Count() > 0)
                {
                    handlers.Elements("remove").Last().AddAfterSelf(new XElement("remove", new XAttribute("name", typeof(ImageEdittingHandler).FullName)));
                }
                else
                {
                    handlers.AddFirst(new XElement("remove", new XAttribute("name", typeof(ImageEdittingHandler).FullName)));
                }
            }
            element = new XElement("add",
                new XAttribute("name", typeof(ImageEdittingHandler).FullName),
                new XAttribute("path", path),
                new XAttribute("type", typeof(ImageEdittingHandler).FullName + "," + typeof(ImageEdittingHandler).Assembly.FullName),
                new XAttribute("verb", "*"),
                new XAttribute("preCondition", "integratedMode"));
            handlers.Add(element);


            //trace
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "register in system.webServer", typeof(ImageEdittingHandler).Name + "  ");


            if (writeToWebConfig)
            {
                document.Save(filePath);

                //trace
                Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "register and saved at" + filePath, typeof(ImageEdittingHandler).Name + "  ");

            }
            return element.ToString();
        }
        public static void UnRegister(string filePath)
        {
            if (string.IsNullOrEmpty(filePath))
            {
                filePath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
            }
            XDocument document = XDocument.Load(filePath);
            XElement configElement = null;
            XElement httpHandlers = XmlHelper.GetElementsOfPath(document.Root, "system.web", "httpHandlers").FirstOrDefault();
            if (httpHandlers != null)
            {
                configElement = httpHandlers.Elements("add").FirstOrDefault(e => e.Attribute("type").Value.Contains(typeof(ImageEdittingHandler).FullName));
                if (configElement != null)
                {
                    configElement.Remove();
                }
            }
            XElement handlers = XmlHelper.GetElementsOfPath(document.Root, "system.webServer", "handlers").FirstOrDefault();
            if (handlers != null)
            {
                configElement = handlers.Elements("remove").FirstOrDefault(e => e.Attribute("name").Value.Contains(typeof(ImageEdittingHandler).FullName));
                if (configElement != null)
                {
                    configElement.Remove();
                }
                configElement = handlers.Elements("add").FirstOrDefault(e => e.Attribute("type").Value.Contains(typeof(ImageEdittingHandler).FullName));
                if (configElement != null)
                {
                    configElement.Remove();
                }
            }
            document.Save(filePath);

            //trace
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "unRegister from web.Congig", typeof(ImageEdittingHandler).Name + "  ");

        }
        public static string GetUrl(string url, float? maximumWidth, float? maximumHeight, float? width, float? height, float? rotateAngle, FlipType flipType)
        {
            string path = ImageEdittingHandler.DefaultPath;
            XDocument document = XDocument.Load(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
            XElement httpHandlers = XmlHelper.CreateIfNotExist(document.Root, "system.web", "httpHandlers");
            var configElement = httpHandlers.Elements("add").FirstOrDefault(e => e.Attribute("type").Value.Contains(typeof(ImageEdittingHandler).FullName));
            if (configElement != null)
            {
                path = configElement.Attribute("path").Value;
            }
            XElement handlers = XmlHelper.CreateIfNotExist(document.Root, "system.webServer", "handlers");
            configElement = handlers.Elements("add").FirstOrDefault(e => e.Attribute("type") != null && e.Attribute("type").Value.Contains(typeof(ImageEdittingHandler).FullName));
            if (configElement != null)
            {
                path = configElement.Attribute("path").Value;
            }

            string queryString = "";
            queryString += (width.HasValue && queryString != "" ? "&" : "") + (width.HasValue ? ImageEdittingHandler.Width + "=" + width.Value : "");
            queryString += (height.HasValue && queryString != "" ? "&" : "") + (height.HasValue ? ImageEdittingHandler.Height + "=" + height.Value : "");
            queryString += (maximumWidth.HasValue && queryString != "" ? "&" : "") + (maximumWidth.HasValue ? ImageEdittingHandler.MaximumWidth + "=" + maximumWidth.Value : "");
            queryString += (maximumHeight.HasValue && queryString != "" ? "&" : "") + (maximumHeight.HasValue ? ImageEdittingHandler.MaximumHeight + "=" + maximumHeight.Value : "");
            queryString += (rotateAngle.HasValue && queryString != "" ? "&" : "") + (rotateAngle.HasValue ? ImageEdittingHandler.RotateAngle + "=" + rotateAngle.Value : "");
            queryString += (flipType != FlipType.None && queryString != "" ? "&" : "") + (flipType != FlipType.None ? ImageEdittingHandler.Flip + "=" + flipType.ToString() : "");
            queryString += (url != null && queryString != "" ? "&" : "") + (url != null ? ImageEdittingHandler.URl + "=" + HttpUtility.UrlEncode(url) : "");
            return path + (queryString != "" ? "?" : "") + queryString;
        }
        public static Bitmap GetBitmap(string url, float? maximumWidth, float? maximumHeight, float? width, float? height, float? rotateAngle, FlipType flipType)
        {
            Bitmap bitmap = null;
            if (url != null)
            {
                WebClient client = new WebClient();
                byte[] bytes = client.DownloadData(url);


                using (MemoryStream msOriginal = new MemoryStream(bytes))
                {
                    bitmap = new Bitmap(Image.FromStream(msOriginal));
                    
                    //trace
                    Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "dowload image data :w=" + bitmap.Width + " ,h=" + bitmap.Height, typeof(ImageEdittingHandler).Name + "  ");
                    
                    
                    //rotate
                    if (rotateAngle.HasValue)
                    {
                        bitmap = ImageEdittingHandler.rotate(bitmap, rotateAngle.Value);
                        
                        //trace
                        Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "rotate=" + rotateAngle.Value + "newW=" + bitmap.Width + " ,newH=" + bitmap.Height, typeof(ImageEdittingHandler).Name + "  ");
                    }



                    //flip
                    if (flipType == FlipType.X)
                    {
                        if (bitmap == null)
                        {
                            bitmap = new Bitmap(bitmap);
                        }
                        bitmap.RotateFlip(RotateFlipType.RotateNoneFlipX);

                        //trace
                        Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "flipX", typeof(ImageEdittingHandler).Name + "  ");

                    }
                    if (flipType == FlipType.Y)
                    {
                        if (bitmap == null)
                        {
                            bitmap = new Bitmap(bitmap);
                        }
                        bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);

                        //trace
                        Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "flipy", typeof(ImageEdittingHandler).Name + "  ");

                    }
                    if (flipType == FlipType.XY)
                    {
                        if (bitmap == null)
                        {
                            bitmap = new Bitmap(bitmap);
                        }
                        bitmap.RotateFlip(RotateFlipType.RotateNoneFlipXY);

                        //trace
                        Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "flipXy", typeof(ImageEdittingHandler).Name + "  ");
                    }

                    //default values
                    float newWidthValue = 0;
                    float newHeightValue = 0;
                    //scale the image in recatngle of maximumwidth & maximum height
                    if (maximumWidth.HasValue && maximumHeight.HasValue)
                    {
                        newWidthValue = maximumWidth.Value;
                        newHeightValue = bitmap.Height * newWidthValue / bitmap.Width;
                        if (newHeightValue > maximumHeight.Value)
                        {
                            newHeightValue = maximumHeight.Value;
                            newWidthValue = bitmap.Width * newHeightValue / bitmap.Height;
                        }

                        //trace
                        Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "scale image to :w=" + newWidthValue + " ,h=" + newHeightValue, typeof(ImageEdittingHandler).Name + "  ");

                    }
                    //scale image with maximumwidth
                    else if (maximumWidth.HasValue)
                    {
                        newWidthValue = maximumWidth.Value;
                        newHeightValue = bitmap.Height * newWidthValue / bitmap.Width;

                        //trace
                        Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "scale image with width :w=" + newWidthValue, typeof(ImageEdittingHandler).Name + "  ");
                    }
                    //scale image with maximumheight
                    else if (maximumHeight.HasValue)
                    {
                        newHeightValue = maximumHeight.Value;
                        newWidthValue = bitmap.Width * newHeightValue / bitmap.Height;

                        //trace
                        Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "scale image with height :H=" + newHeightValue, typeof(ImageEdittingHandler).Name + "  ");

                    }
                    //set to original width & height
                    if (!maximumWidth.HasValue && !maximumHeight.HasValue)
                    {
                        newWidthValue = bitmap.Width;
                        newHeightValue = bitmap.Height;
                    }
                    //set to new width and original height if no maximum height specified
                    if (width.HasValue)
                    {
                        newWidthValue = width.Value;

                        //trace
                        Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "noScale(newWidth) newW=" + newWidthValue, typeof(ImageEdittingHandler).Name + "  ");

                    }
                    //set to new height and original width if no maximum width specified
                    if (height.HasValue)
                    {
                        newHeightValue = height.Value;

                        //trace
                        Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "noScale(newHeight) newH=" + newHeightValue, typeof(ImageEdittingHandler).Name + "  ");

                    }
                    Bitmap bitmaptemp = new Bitmap((int)newWidthValue, (int)newHeightValue);
                    bitmaptemp.SetResolution(bitmap.HorizontalResolution, bitmap.VerticalResolution);
                    using (Graphics graphics = Graphics.FromImage(bitmaptemp))
                    {
                        graphics.SmoothingMode = SmoothingMode.HighQuality;
                        graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                        graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
                        graphics.CompositingQuality = CompositingQuality.HighQuality;

                        //drow with new zize
                        graphics.DrawImage(bitmap, new Rectangle(0, 0, (int)newWidthValue, (int)newHeightValue));

                    }
                    bitmap.Dispose();
                    bitmap = bitmaptemp;
                    
                    //trace
                    Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "draw image on graphics: newW=" + bitmap.Width + ",newH=" + bitmap.Height + "resolution=(" + bitmap.HorizontalResolution + "," + bitmap.VerticalResolution + ")", typeof(ImageEdittingHandler).Name + "  ");
                }
            }
            return bitmap;
        }
       
        #endregion

        public bool IsReusable
        {
            get { return true; }
        }
        public void ProcessRequest(HttpContext context)
        {
            string uriParam = context.Request.QueryString[ImageEdittingHandler.URl];
            string maximumWidthParam = context.Request.QueryString[ImageEdittingHandler.MaximumWidth];
            string maximumHeightParam = context.Request.QueryString[ImageEdittingHandler.MaximumHeight];
            string widthParam = context.Request.QueryString[ImageEdittingHandler.Width];
            string heightParam = context.Request.QueryString[ImageEdittingHandler.Height];
            string rotateAngleParam = context.Request.QueryString[ImageEdittingHandler.RotateAngle];
            string flipParam = context.Request.QueryString[ImageEdittingHandler.Flip];

            string uriValue = null;
            if (!string.IsNullOrEmpty(uriParam))
            {
                uriValue = UrlHelper.GetCompleteUri(HttpUtility.UrlDecode(uriParam));
            }

            //cashing
            DateTime lastModifieddate = UrlHelper.GetResourceModifiedDate(uriValue);

            if(ImageEdittingHandler.IsResourceModified(lastModifieddate,context.Request))
            {
                float? widthValue = (string.IsNullOrEmpty(widthParam) ? null : (float?)Convert.ToSingle(widthParam));
                float? heightValue = (string.IsNullOrEmpty(heightParam) ? null : (float?)Convert.ToSingle(heightParam));
                float? maximumWidthValue = (string.IsNullOrEmpty(maximumWidthParam) ? null : (float?)Convert.ToSingle(maximumWidthParam));
                float? maximumHeightValue = (string.IsNullOrEmpty(maximumHeightParam) ? null : (float?)Convert.ToSingle(maximumHeightParam));
                float? rotateAngleValue = (string.IsNullOrEmpty(rotateAngleParam) ? null : (float?)Convert.ToSingle(rotateAngleParam));
                FlipType flipValue = (string.IsNullOrEmpty(flipParam) ? FlipType.None : (FlipType)Enum.Parse(typeof(FlipType), flipParam.ToUpper()));

                using (Bitmap bitmap = ImageEdittingHandler.GetBitmap(uriValue, maximumWidthValue, maximumHeightValue, widthValue, heightValue, rotateAngleValue, flipValue))
                {
                    using (MemoryStream msDestination = new MemoryStream())
                    {
                        // memory stream required because of bug in GDI+ when saving PNG directly to Response.OutputStream
                        if (bitmap != null)
                        {
                            bitmap.Save(msDestination, ImageFormat.Png);
                            context.Response.ContentType = "image/png";
                            msDestination.WriteTo(context.Response.OutputStream);
                        }
                    }
                }


                //trace
                Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "build bitmap", typeof(ImageEdittingHandler).Name + "  ");

            }
            else
            {
                context.Response.StatusCode = 304;
                context.Response.StatusDescription = "Not Modified";
                // Explicitly set the Content-Length header so the client doesn't wait for
                //  content but keeps the connection open for other requests 
                context.Response.AddHeader("Content-Length", "0");

                //trace
                Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "no build for bitmap,send not modified to browser", typeof(ImageEdittingHandler).Name + "  ");

            }

            //cashing
            context.Response.Cache.SetCacheability(HttpCacheability.Public);
            context.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
            context.Response.Cache.SetMaxAge(new TimeSpan(0, 0, 0));
            try
            {
                context.Response.Cache.SetLastModified(lastModifieddate);
            }
            catch
            {
            }
            context.Response.Cache.SetETag("\"ImageEdittingHandler" + lastModifieddate.ToString("yyyyMMddHHmmss") + "\"");
            context.Response.Cache.VaryByParams["*"] = true;
            context.Response.Cache.VaryByHeaders["If-Modified-Since"] = true;
            context.Response.Cache.VaryByHeaders["If-None-Match"] = true;
            context.Response.End();

            //trace
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "write response cash headers", typeof(ImageEdittingHandler).Name + "  ");

        }
    }
}
